/*
 * Copyright (c) 2018, apexes.net. All rights reserved.
 *
 *         http://www.apexes.net
 *
 */
package net.apexes.maven;

import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.maven.artifact.repository.metadata.Metadata;
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;

/**
 * @author hedyn
 */
public class MavenDownloader {

    private final Path downloadPath;
    private final List<Dependency> dependencyList;
    private final List<URI> repositoryUriList;
    private final OkHttpClient httpClient;
    private final DownloadCallback downloadCallback;

    public MavenDownloader(List<URI> repositoryUriList,
                           List<Dependency> dependencyList,
                           Path downloadPath,
                           OkHttpClient httpClient,
                           DownloadCallback downloadCallback) {
        this.repositoryUriList = repositoryUriList;
        this.dependencyList = dependencyList;
        this.downloadPath = downloadPath;
        this.httpClient = httpClient;
        this.downloadCallback = downloadCallback;
    }

    public List<Path> downloadAll() throws IOException {
        List<Path> pathList = new ArrayList<>();
        for(Dependency dependency : dependencyList) {
            Path theDownloadPath = UrlUtils.formatLocalPath(downloadPath, dependency);
            if (download(dependency, theDownloadPath)) {
                pathList.add(theDownloadPath);
                if (downloadCallback != null) {
                    downloadCallback.onSuccess(dependency, theDownloadPath);
                }
            } else if (downloadCallback != null) {
                downloadCallback.onFailure(dependency);
            }
        }
        return pathList;
    }

    private boolean download(Dependency dependency, Path theDownloadPath) {
        if(Files.exists(theDownloadPath)) {
            return true;
        }
        for (URI repositoryUri : repositoryUriList) {
            try {
                if (download(repositoryUri, dependency, theDownloadPath)) {
                    if (downloadCallback != null) {
                        downloadCallback.onSuccess(repositoryUri, dependency, theDownloadPath);
                    }
                    return true;
                }
                if (downloadCallback != null) {
                    downloadCallback.onFailure(repositoryUri, dependency, new IOException("Not found"));
                }
            } catch (IOException e) {
                if (downloadCallback != null) {
                    downloadCallback.onFailure(repositoryUri, dependency, e);
                }
            }
        }
        return false;
    }

    private boolean download(URI repositoryUri, Dependency dependency, Path theDownloadPath) throws IOException {
        Metadata metadata = null;
        Metadata artifactMetadata = null;
        URI groupMetaURI = UrlUtils.buildGroupMetaURI(repositoryUri, dependency);

        // Try to parse repository meta
        metadata = getMetadata(httpClient, groupMetaURI);
        if (metadata != null) {
            URI artifactMetaURI = UrlUtils.buildArtifactMetaURI(repositoryUri, metadata, dependency);
            artifactMetadata = getMetadata(httpClient, artifactMetaURI);
        }

        // Build artifact url
        URI artifactJarURI = UrlUtils.buildArtifactJarURI(repositoryUri, artifactMetadata, dependency);
        Request request = new Request.Builder().url(HttpUrl.get(artifactJarURI)).build();
        Response artifactJarResponse = httpClient.newCall(request).execute();
        if (artifactJarResponse.isSuccessful()) {
            Path parentPath = theDownloadPath.getParent();
            if (parentPath != null && !Files.exists(parentPath)) {
                Files.createDirectories(parentPath);
            }
            Files.copy(artifactJarResponse.body().byteStream(), theDownloadPath,
                    StandardCopyOption.REPLACE_EXISTING);
            return true;
        } else {
            return false;
        }
    }

    private static Metadata getMetadata(OkHttpClient client, URI url) throws IOException {
        Request request = new Request.Builder()
                .url(HttpUrl.get(url))
                .build();
        try(Response response = client.newCall(request).execute()) {
            if(response.isSuccessful()) {
                return new MetadataXpp3Reader().read(response.body().charStream());
            }
        } catch (XmlPullParserException e) {
            throw new IOException(e);
        }
        return null;
    }
}
